package com.sccba.uop.demo;

import com.fasterxml.jackson.databind.node.ObjectNode;
import com.sccba.uop.demo.common.constant.RequestHeaderField;
import com.sccba.uop.demo.common.constant.ResponseHeaderField;
import com.sccba.uop.demo.util.DateUtils;
import com.sccba.uop.demo.util.JsonUtils;
import com.sccba.uop.demo.util.RandomUtils;
import com.sccba.uop.demo.util.SignatureUtils;
import com.sccba.uop.demo.util.algorithm.*;
import okhttp3.*;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.TimeUnit;

/**
 * 国密版本的请求demo。
 * <p>
 * 注意事项：
 * 本demo不具备完整的代码封装，不可直接用于生产环境，仅用于演示如何请求银行统一开放平台系统标准规范的API接口；
 *
 * @author 统一开放平台
 * @version 1.0.0
 */
public class SM2Demo {

    private static final Logger LOGGER = LoggerFactory.getLogger(SM2Demo.class);

    public static void main(String[] args) throws IOException {
        LOGGER.info("----------------国密版本DEMO BEGIN---------------");

        //API接口地址
        String url = "https://802.ebanktest.com.cn:3802/uop/api/oauth/accessToken/v1";
        LOGGER.info("请求地址：{}", url);
        //应用ID，由开放平台发放
        String appId = "80292180174";
        LOGGER.info("请求发起应用：{}", appId);
        //API CODE，由开放平台发放
        String apiCode = "OPC108960202000180200700";
        LOGGER.info("请求API-CODE：{}", apiCode);
        //请求时间
        String datetime = DateUtils.getTimestamp();
        LOGGER.info("请求发起时间：{}", datetime);
        //唯一交易流水号
        String uniqueNo = RandomUtils.uuidNonLine();
        LOGGER.info("请求流水号：{}", uniqueNo);

        //读取银行非对称公钥，用于加密对称密钥
        byte[] pubKey = SM2Utils.readPublicKeyFromCer("src/main/resources/sm2/bank-test-sm2-pub_2023.cer");
        LOGGER.debug("银行公钥(base64)：{}", Base64Utils.encodeToString(pubKey));
        //读取第三方自身私钥（或来源于别的密钥管理系统），用于签名
        //提供了一个测试私钥
        byte[] privKey = ByteUtils.fromHexString("006CC8EF4626C92E5648C9BC830F96B3B02E8A24329A469582452572A7B74D4072");
        LOGGER.debug("商户私钥(base64)：{}", Base64Utils.encodeToString(privKey));
        //生成对称密钥
        byte[] aesKey = SM4Utils.creteKey();
        LOGGER.debug("请求时的随机对称密钥(base64)：{}", Base64Utils.encodeToString(aesKey));
        //使用银行公钥加密对称密钥
        byte[] cipherAesReqKey = SM2Utils.encrypt(pubKey, aesKey);
        //对称密钥密文进行BASE64编码
        String base64CipherAesReqKey = Base64Utils.encodeToString(cipherAesReqKey);

        LOGGER.info("------------------------------------------------");
        /**
         * 构造HTTP请求头,demo中仅设置了必填参数
         * 完整的请求头参数列表可查看{@link RequestHeaderField}枚举类。
         */
        Headers.Builder reqHeaders = new Headers.Builder().add("Content-Type", "application/json;charset=utf-8")
                                                          /* 必填参数 */
                                                          .add(RequestHeaderField.X_UOP_API_CODE.getName(), apiCode)
                                                          .add(RequestHeaderField.X_UOP_APP_ID.getName(), appId)
                                                          .add(RequestHeaderField.X_UOP_TIMESTAMP.getName(), datetime)
                                                          .add(RequestHeaderField.X_UOP_UNIQUE_NO.getName(), uniqueNo)
                                                          /* 非必填参数 */
                                                          .add(RequestHeaderField.X_UOP_AUTH_TOKEN.getName(), "")
                                                          .add(RequestHeaderField.X_UOP_IP.getName(), "")
                                                          .add(RequestHeaderField.X_UOP_MAC.getName(), "")
                                                          .add(RequestHeaderField.X_UOP_LATITUDE.getName(), "")
                                                          .add(RequestHeaderField.X_UOP_LONGITUDE.getName(), "")
                                                          .add(RequestHeaderField.X_UOP_TERM_ID.getName(), "")
                                                          .add(RequestHeaderField.X_UOP_TERM_MODE.getName(), "")
                                                          .add(RequestHeaderField.X_UOP_TERM_TYPE.getName(), "")
                                                          .add(RequestHeaderField.X_UOP_SIM_NO.getName(), "");
        /**
         * 构造HTTP请求体
         */
        ObjectNode reqBody = JsonUtils.objectMapper.createObjectNode();
        reqBody.put("code", "1001");

        String jsonReqBody = reqBody.toString();
        LOGGER.info("请求报文明文(json)：{}", jsonReqBody.replaceAll("[\n\r]", ""));

        //对请求报文进行对称加密
        byte[] cipherReqBody = SM4Utils.encrypt(aesKey, jsonReqBody.getBytes(StandardCharsets.UTF_8));
        //对请求报文密文进行BASE64编码
        String base64CipherReqBody = Base64Utils.encodeToString(cipherReqBody);
        LOGGER.info("请求报文密文(base64)：{}", base64CipherReqBody);
        //构造签名数据
        String reqSignContent = SignatureUtils.buildReqSignContentWithSM3(reqHeaders, jsonReqBody);
        LOGGER.info("请求报文签名数据：{}", reqSignContent);
        //计算签名值
        byte[] reqSignature = SM2Utils.signWithSm3(privKey, reqSignContent.getBytes(StandardCharsets.UTF_8));
        //对签名值进行BASE64编码
        String base64ReqSignature = Base64Utils.encodeToString(reqSignature);
        LOGGER.info("请求报文签名值(base64)：{}", base64ReqSignature);
        LOGGER.info("------------------------------------------------");
        //必填参数
        //设置对称密钥密文和请求报文签名值
        reqHeaders.add(RequestHeaderField.X_UOP_ENCRYPTED_KEY.getName(), base64CipherAesReqKey)
                  .add(RequestHeaderField.X_UOP_SIGNATURE.getName(), base64ReqSignature);

        /**
         * 发送请求
         */
        OkHttpClient okHttpClient = new OkHttpClient.Builder().connectTimeout(3000, TimeUnit.MILLISECONDS)
                                                              .readTimeout(30000, TimeUnit.MILLISECONDS)
                                                              .build();
        Request postRequest = new Request.Builder().url(url)
                                                   //放入请求头
                                                   .headers(reqHeaders.build())
                                                   //放入请求密文
                                                   .post(RequestBody.create(base64CipherReqBody, MediaType.parse("application/json;charset=utf-8")))
                                                   .build();
        LOGGER.debug("原始HTTP请求数据：{},body=[{}]", postRequest, base64CipherReqBody);
        Response response = okHttpClient.newCall(postRequest).execute();

        String respBody = response.body().string();
        LOGGER.debug("原始HTTP响应数据：{},headers=[{}],body=[{}]", response, response.headers().toString().replaceAll("[\n\r]", ","), respBody);
        LOGGER.info("------------------------------------------------");

        //获取响应报文使用的对称密钥
        String base64CipherAesRespKey = response.header(ResponseHeaderField.X_UOP_ENCRYPTED_KEY.getName());
        //使用三方私钥解密对称密钥
        byte[] aesRespKey = SM2Utils.decrypt(privKey, Base64Utils.decode(base64CipherAesRespKey));
        LOGGER.debug("响应时的随机对称密钥(base64)：{}", Base64Utils.encodeToString(aesRespKey));

        //获取响应报文密文
        String base64CipherRespBody = respBody;
        LOGGER.info("响应报文密文(base64)：{}", base64CipherRespBody);
        //使用对称密钥解密响应报文
        byte[] plainRespBody = SM4Utils.decrypt(aesRespKey, Base64Utils.decode(base64CipherRespBody));
        String jsonRespBody = JsonUtils.objectMapper.readTree(plainRespBody).toString();
        LOGGER.info("响应报文明文(json)：{}", jsonRespBody);
        //构造响应报文签名数据
        String respSignContent = SignatureUtils.buildRespSignContentWithSM3(response.headers(), plainRespBody);
        LOGGER.info("响应报文签名数据：{}", respSignContent);
        //获取响应签名
        String base64RespSignature = response.header(ResponseHeaderField.X_UOP_SIGNATURE.getName());
        LOGGER.info("响应报文签名值(base64)：{}", base64RespSignature);
        //校验签名值
        boolean verifyResult = SM2Utils.verifyWithSm3(pubKey, respSignContent.getBytes(StandardCharsets.UTF_8), Base64Utils.decode(base64RespSignature));
        LOGGER.info("验签结果：{}", verifyResult);
        LOGGER.info("-----------------国密版本DEMO END-----------------");
    }

}
